/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.qijia.common.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.PortResolver;
import org.springframework.security.web.PortResolverImpl;
import org.springframework.security.web.authentication.AbstractProcessingFilter;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.Assert;

import com.qijia.common.utils.UrlUtil;

/**
 * Notes about my changes: Copy from original RememberMeProcessingFilter. Then
 * merge with some processing logic in AbstractProcessingFilter, to make it work
 * the same with AbstractProcessingFilter, i.e. redirect to a specified url
 * after a successful authentication, so the united post login processing will
 * be applied, e.g. auditing. Most changes are appended in the end of file.
 * 
 * @author Legend
 * 
 */
/**
 * Detects if there is no <code>Authentication</code> object in the
 * <code>SecurityContext</code>, and populates it with a remember-me
 * authentication token if a
 * {@link org.acegisecurity.ui.rememberme.RememberMeServices}implementation so
 * requests.
 * <p>
 * Concrete <code>RememberMeServices</code> implementations will have their
 * {@link
 * org.acegisecurity.ui.rememberme.RememberMeServices#autoLogin(HttpServletRequest,
 * HttpServletResponse)} method called by this filter. The
 * <code>Authentication</code> or <code>null</code> returned by that method
 * will be placed into the <code>SecurityContext</code>. The
 * <code>AuthenticationManager</code> will be used, so that any concurrent
 * session management or other authentication-specific behaviour can be
 * achieved. This is the same pattern as with other authentication mechanisms,
 * which call the <code>AuthenticationManager</code> as part of their
 * contract.
 * </p>
 * <p>
 * If authentication is successful, an {@link
 * org.acegisecurity.event.authentication.InteractiveAuthenticationSuccessEvent}
 * will be published to the application context. No events will be published if
 * authentication was unsuccessful, because this would generally be recorded via
 * an <code>AuthenticationManager</code> -specific application event.
 * </p>
 * <p>
 * <b>Do not use this class directly. </b> Instead configure
 * <code>web.xml</code> to use the {@link
 * org.acegisecurity.util.FilterToBeanProxy}.
 * </p>
 * 
 * @author Ben Alex
 * @version $Id: RememberMeProcessingFilter.java 1496 2006-05-23 13:38:33 +0000
 *          (Tue, 23 May 2006) benalex $
 */
public class RememberMeProcessingFilter implements Filter, InitializingBean,
		ApplicationEventPublisherAware {
	// ~ Static fields/initializers
	// =====================================================================================

	private static final Log			logger				= LogFactory
																	.getLog(RememberMeProcessingFilter.class);

	// ~ Instance fields
	// ================================================================================================

	private AuthenticationManager		authenticationManager;

	private String						defaultTargetUrl;

	private ApplicationEventPublisher	eventPublisher;

	// ~ Methods
	// ========================================================================================================

	private PortResolver				portResolver		= new PortResolverImpl();

	private RememberMeServices			rememberMeServices	= new NullRememberMeServices();

	public void afterPropertiesSet() throws Exception {
		Assert.notNull(rememberMeServices, "RememberMeServices required");
		Assert.notNull(authenticationManager, "AuthenticationManager required");
	}

	/**
	 * Does nothing - we rely on IoC lifecycle services instead.
	 */
	public void destroy() {
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		if (!(request instanceof HttpServletRequest)) {
			throw new ServletException("Can only process HttpServletRequest");
		}

		if (!(response instanceof HttpServletResponse)) {
			throw new ServletException("Can only process HttpServletResponse");
		}

		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			Authentication rememberMeAuth = rememberMeServices.autoLogin(
					httpRequest, httpResponse);

			if (rememberMeAuth != null) {
				// Attempt authenticaton via AuthenticationManager
				try {
					authenticationManager.authenticate(rememberMeAuth);

					// Store to SecurityContextHolder
					SecurityContextHolder.getContext().setAuthentication(
							rememberMeAuth);

					if (logger.isDebugEnabled()) {
						logger
								.debug("SecurityContextHolder populated with remember-me token: '"
										+ SecurityContextHolder.getContext()
												.getAuthentication() + "'");
					}

					// Fire event
					if (this.eventPublisher != null) {
						eventPublisher
								.publishEvent(new InteractiveAuthenticationSuccessEvent(
										SecurityContextHolder.getContext()
												.getAuthentication(), this
												.getClass()));
					}

					// Start of change, Legend Peng.
					// rememberme works
					successfulAuthentication((HttpServletRequest) request,
							(HttpServletResponse) response, rememberMeAuth);
					return;
					// End of change, Legend Peng.
				} catch (AuthenticationException authenticationException) {
					if (logger.isDebugEnabled()) {
						logger
								.debug(
										"SecurityContextHolder not populated with remember-me token, as AuthenticationManager rejected Authentication returned by RememberMeServices: '"
												+ rememberMeAuth
												+ "'; invalidating remember-me token",
										authenticationException);
					}

					rememberMeServices.loginFail(httpRequest, httpResponse);
				}
			}

			chain.doFilter(request, response);
		} else {
			if (logger.isDebugEnabled()) {
				logger
						.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
								+ SecurityContextHolder.getContext()
										.getAuthentication() + "'");
			}

			chain.doFilter(request, response);
		}
	}

	public RememberMeServices getRememberMeServices() {
		return rememberMeServices;
	}

	/**
	 * Does nothing - we rely on IoC lifecycle services instead.
	 * 
	 * @param ignored
	 *            not used
	 * 
	 * @throws ServletException
	 *             DOCUMENT ME!
	 */
	public void init(FilterConfig ignored) throws ServletException {
	}

	protected void sendRedirect(HttpServletRequest request,
			HttpServletResponse response, String url) throws IOException {
		if (!url.startsWith("http://") && !url.startsWith("https://")) {
			url = request.getContextPath() + url;
		}

		response.sendRedirect(response.encodeRedirectURL(url));
	}

	/**
	 * End of change, Legend Peng.
	 */

	public void setApplicationEventPublisher(
			ApplicationEventPublisher eventPublisher) {
		this.eventPublisher = eventPublisher;
	}

	public void setAuthenticationManager(
			AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	public void setDefaultTargetUrl(String defaultTargetUrl) {
		Assert.isTrue(defaultTargetUrl.startsWith("/")
				| defaultTargetUrl.startsWith("http"),
				"defaultTarget must start with '/' or with 'http(s)'");
		this.defaultTargetUrl = UrlUtil.appendParamToUrl(defaultTargetUrl,
				"isRememberMe", "true");
	}

	public void setRememberMeServices(RememberMeServices rememberMeServices) {
		this.rememberMeServices = rememberMeServices;
	}

	/**
	 * Start of change, Legend Peng. This code is copied from
	 * AbstractProcessingFilter.
	 */
	/**
	 * Redirect to defaultTargetUrl after a successful rememberme authentication
	 * 
	 * @param request
	 * @param response
	 * @param authResult
	 * @throws IOException
	 */
	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, Authentication authResult)
			throws IOException {
		SavedRequest savedRequest = new DefaultSavedRequest(request, portResolver);

		// Store the HTTP request itself. Used by AbstractProcessingFilter
		// for redirection after successful authentication (SEC-29)
		request.getSession(true).setAttribute(
				AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, savedRequest);

		sendRedirect(request, response, defaultTargetUrl);
	}

}
